﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace Percolation.Gui
{
    [Serializable]
    public class Experiment
    {
        public NetInterface Net { get; private set; }
        public BoltStatistics BoltStats { get; private set; }
        public HKStatistics HKStats { get; private set; }
        public NetStatistics NetStats { get; private set; }

        public Experiment(NetInterface net)
        {
            Net = net;
            BoltStats = null;
            HKStats = null;
            NetStats = null;
        }

        public bool isProcessed()
        {
            return (NetStats != null) && (HKStats != null) && (BoltStats != null);
        }

        public void addNetStatistics(NetStatistics stats)
        {
            NetStats = stats;
        }

        public void addBoltStatistics(BoltStatistics stats)
        {
            BoltStats = stats;
        }

        public void addHKStatistics(HKStatistics stats)
        {
            HKStats = stats;
        }
    }

    [Serializable]
    public class PackStatistics
    {
        public double NetAvgBlackCellRow { get; private set; }
        public double NetAvgBlackCellColumn { get; private set; }
        public double NetAvgNullRowCount { get; private set; }
        public double NetAvgNullColumnCount { get; private set; }
        public double NetAvgBlackCellCount { get; private set; }

        public double HKAvgClusterCount { get; private set; }
        public double HKAvgClusterSize { get; private set; }
        public double HKPercolationProbability { get; private set; }
        public double HKAvgPercolationClusterSize { get; private set; }

        public double BoltAvgAddedCellCount { get; private set; }
        public double BoltAvgRouteLength { get; private set; }
        public double BoltAvgRouteClusters { get; private set; }
        public int BoltMaxRouteClusters { get; private set; }
        public double BoltAddedCellsConcentration { get; private set; }

        public PackStatistics(ExperimentsPack pack)
        {
            NetAvgBlackCellRow = 0;
            NetAvgBlackCellColumn = 0;
            NetAvgNullRowCount = 0;
            NetAvgNullColumnCount = 0;
            NetAvgBlackCellCount = 0;

            HKAvgClusterCount = 0;
            HKAvgClusterSize = 0;
            HKPercolationProbability = 0;
            HKAvgPercolationClusterSize = 0;

            BoltAvgAddedCellCount = 0;
            BoltAvgRouteLength = 0;
            BoltAvgRouteClusters = 0;
            BoltMaxRouteClusters = 0;
            BoltAddedCellsConcentration = 0;

            foreach (Experiment exp in pack.Experiments)
            {
                NetAvgBlackCellRow += exp.NetStats.AvgBlackCellRow;
                NetAvgBlackCellColumn += exp.NetStats.AvgBlackCellColumn;
                NetAvgNullRowCount += exp.NetStats.NullRowCount;
                NetAvgNullColumnCount += exp.NetStats.NullColumnCount;
                NetAvgBlackCellCount += exp.NetStats.BlackCellCount;

                HKAvgClusterCount += exp.HKStats.ClusterCount;
                HKAvgClusterSize += exp.HKStats.ClusterAvgSize;
                HKPercolationProbability += exp.HKStats.Percolation ? 1 : 0;
                HKAvgPercolationClusterSize += exp.HKStats.PercolationClusterSize;

                if (exp.BoltStats != null)
                {
                    BoltAvgAddedCellCount += exp.BoltStats.PathStatisticsList[0].Value.AddedCellsCount;
                    BoltAvgRouteLength += exp.BoltStats.PathStatisticsList[0].Value.RouteLength;
                    BoltAvgRouteClusters += exp.BoltStats.PathStatisticsList[0].Value.AvgRouteClusters;
                    if (exp.BoltStats.PathStatisticsList[0].Value.MaxRouteClusters > BoltMaxRouteClusters)
                        BoltMaxRouteClusters = exp.BoltStats.PathStatisticsList[0].Value.MaxRouteClusters;

                    BoltAddedCellsConcentration += exp.BoltStats.PathStatisticsList[0].Value.AddedCellsConcentration;
                }
            }

            int count = pack.Experiments.Count;
            NetAvgBlackCellRow /= count;
            NetAvgBlackCellColumn /= count;
            NetAvgNullRowCount /= count;
            NetAvgNullColumnCount /= count;
            NetAvgBlackCellCount /= count;

            HKAvgClusterCount /= count;
            HKAvgClusterSize /= count;
            if (HKPercolationProbability > 0)
                HKAvgPercolationClusterSize /= HKPercolationProbability;
            HKPercolationProbability /= count;

            BoltAvgAddedCellCount /= count;
            BoltAvgRouteLength /= count;
            BoltAvgRouteClusters /= count;
            BoltAddedCellsConcentration /= count;
        }
    }

    [Serializable]
    public class ExperimentsPack
    {

        public ExperimentsPack(NetPackSettings settings)
        {
            Settings = settings;
            Experiments = new List<Experiment>();
            PackStats = null;
            Initialize();
        }

        public bool isProcessed()
        {
            bool expProcessed = true;
            foreach (Experiment e in Experiments)
                expProcessed &= e.isProcessed();

            return expProcessed && (PackStats != null);
        }

        public void calculatePackStatistics()
        {
            PackStats = new PackStatistics(this);
        }

        public String GetDescription()
        {
            return Settings.GetDescription();
        }

        public String GetShortDescription()
        {
            return Settings.GetShortDescription();
        }

        public List<Experiment> Experiments { get; private set; }
        public NetPackSettings Settings { get; private set; }
        public PackStatistics PackStats { get; private set; }

        private void Initialize()
        {
            int count = 1;
            if (Settings.NetType == NetTypeEnum.Random || Settings.NetType == NetTypeEnum.VerGradient
                || Settings.NetType == NetTypeEnum.HorGradient)
                count = Settings.Count;

            NetParams netParams = Settings.getNetParams();
            for (int i = 0; i < count; ++i)
                Experiments.Add(new Experiment(NetGenerator.generate(netParams)));
        }
    }

    public enum RangeTypeEnum
    {
        [DescriptionAttribute("Random net range")]
        RandomRange = 0,
        [DescriptionAttribute("Horizontal gradient net range")]
        HorGradientRange = 1,
        [DescriptionAttribute("Vertical gradient net range")]
        VerGradientRange = 2,
        [DescriptionAttribute("Custom range")]
        CustomRange = 3
    }

    [Serializable]
    public sealed class ExperimentsRange
    {
        public List<ExperimentsPack> Packs { get; private set; }
        public NetRangeSettings Settings { get; private set; }

        public ExperimentsRange(NetRangeSettings settings)
        {
            Packs = new List<ExperimentsPack>();
            Settings = settings;
            Initialize();
        }

        public bool isProcessed()
        {
            bool packProcessed = true;
            foreach (ExperimentsPack p in Packs)
                packProcessed &= p.isProcessed();

            return packProcessed;
        }

        public String GetDescription()
        {
            String result = Settings.Type.GetString();
            if (Settings.Type == RangeTypeEnum.CustomRange)
                return result;

            result += " (" + Settings.NetWidth.ToString() + "x" + Settings.NetHeight.ToString() + ")";
            return result;
        }

        public String GetShortDescription()
        {
            if (Settings.Type == RangeTypeEnum.CustomRange)
                throw new ApplicationException("Internal error!");

            switch (Settings.Type)
            {
                case RangeTypeEnum.RandomRange:
                    return "Rnd (" + Settings.NetWidth.ToString() + "x" + Settings.NetHeight.ToString() + ")";
                case RangeTypeEnum.HorGradientRange:
                    return "HGr (" + Settings.NetWidth.ToString() + "x" + Settings.NetHeight.ToString() + "; " + Settings.GradProbability.ToString("F2") + ")";
                case RangeTypeEnum.VerGradientRange:
                    return "VGr (" + Settings.NetWidth.ToString() + "x" + Settings.NetHeight.ToString() + "; " + Settings.GradProbability.ToString("F2") + ")";
                default:
                    return "none";
            }
        }

        public void Initialize()
        {
            if (Settings.Type != RangeTypeEnum.CustomRange)
            {
                for (int i = 0; i < Settings.ProbabilityList.Count; ++i)
                    Packs.Add(new ExperimentsPack(Settings.getPackSettings(i)));
            }
            else
            {
                for (int i = 0; i < Settings.PackSettingsList.Count; ++i)
                    Packs.Add(new ExperimentsPack(Settings.getPackSettings(i)));
            }
        }
    }

}
